package com.slipper.groovy.service;

import com.slipper.PaasContext;
import com.slipper.core.similarity.SimilarityUtil;
import com.slipper.datasource.DatasourceUtil;
import com.slipper.exception.CommonException;
import com.slipper.groovy.util.*;
import com.slipper.groovy.writer.PrintWriter;
import com.slipper.core.gson.GsonUtil;
import com.slipper.system.codeless.bo.ScriptExecuteBO;
import com.slipper.system.codeless.bo.ScriptGroovyBO;
import com.slipper.system.codeless.service.ScriptExecuteService;
import com.slipper.system.codeless.service.ScriptGroovyService;
import com.slipper.util.*;
import groovy.lang.Binding;
import groovy.lang.GroovyShell;
import org.codehaus.groovy.ast.expr.*;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.customizers.SecureASTCustomizer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.script.ScriptEngineManager;
import java.io.File;
import java.io.Writer;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class GroovyService {
    private static final Logger logger = LoggerFactory.getLogger(GroovyService.class);
    private static final List<String> utilList = new ArrayList<>() {{
        add("DataModelUtil");
        add("DatasourceUtil");
        add("JDBCUtil");
        add("FtpUtil");
        add("ScriptUtil");
        add("JsonUtil");
        add("Tool");
        add("UUIDUtil");
        add("HttpUtil");
        add("DateUtil");
        add("AffixUtil");
        add("OfficeUtil");
        add("SimilarityUtil");
        add("DictionaryUtil");
        add("SystemUtil");
        add("CacheUtil");
    }};

    public static Object runById(String id) {
        return runById(id, null, null);
    }

    public static Object runById(String id, Map<String, Object> data) {
        return runById(id, data, null);
    }

    public static Object runById(String id, Writer writer) {
        return runById(id, null, writer);
    }

    public static Object runById(String id, Map<String, Object> data, Writer writer) {
        ScriptExecuteBO scriptExecuteBO = new ScriptExecuteBO();
        scriptExecuteBO.setGroovyId(id);
        scriptExecuteBO.setParams(GsonUtil.toJson(data));
        scriptExecuteBO.setStartTime(System.currentTimeMillis());
        try {
            ScriptGroovyBO scriptGroovy = PaasContext.getBean(ScriptGroovyService.class).queryBy(id);
            if (scriptGroovy == null) throw new CommonException("脚本不存在！");
            if (Tool.isBlank(scriptGroovy.getScript())) throw new CommonException("脚本内容为空！");

            logger.info("执行脚本：{} ({})", scriptGroovy.getName(), id);
            scriptExecuteBO.setResult("成功");
            return run(scriptGroovy.getName(), scriptGroovy.getScript(), data, writer);
        } catch (Exception e) {
            scriptExecuteBO.setResult("失败");
            scriptExecuteBO.setMessage(e.getMessage());
            throw e;
        } finally {
            scriptExecuteBO.setEndTime(System.currentTimeMillis());
            scriptExecuteBO.setTakeTime(scriptExecuteBO.getEndTime() - scriptExecuteBO.getStartTime());
            PaasContext.getBean(ScriptExecuteService.class).save(scriptExecuteBO);
        }
    }

    public static Object run(String script) {
        return run(script, null, null);
    }

    public static Object run(String script, Map<String, Object> data) {
        return run(script, data, null);
    }

    public static Object run(String script, Writer writer) {
        return run(script, null, writer);
    }

    public static Object run(String script, Map<String, Object> data, Writer writer) {
        return run(null, script, data, writer);
    }

    public static Object run(String scriptName, String script, Map<String, Object> data, Writer writer) {
        PrintWriter printWriter = new PrintWriter(scriptName, writer);
        long startTime = System.currentTimeMillis();
        JDBCUtil jdbcUtil = new JDBCUtil();
        try (FtpUtil ftpUtil = new FtpUtil()) {
            if (writer == null) {
                printWriter.println("执行开始...");
                printWriter.println("执行参数：" + (Tool.isBlank(data) ? "" : GsonUtil.toJson(data)));
            }

            Binding binding = new Binding();
            binding.setProperty("out", printWriter);
            binding.setVariable("args", data);
            binding.setVariable("DataModelUtil", DataModelUtil.class);
            binding.setVariable("DatasourceUtil", DatasourceUtil.class);
            binding.setVariable("JDBCUtil", jdbcUtil);
            binding.setVariable("FtpUtil", ftpUtil);
            binding.setVariable("ScriptUtil", new ScriptUtil(writer));
            binding.setVariable("JsonUtil", GsonUtil.class);
            binding.setVariable("Tool", Tool.class);
            binding.setVariable("UUIDUtil", UUID.class);
            binding.setVariable("HttpUtil", HttpUtil.class);
            binding.setVariable("DateUtil", DateUtil.class);
            binding.setVariable("AffixUtil", AffixUtil.class);
            binding.setVariable("OfficeUtil", OfficeUtil.class);
            binding.setVariable("SimilarityUtil", SimilarityUtil.class);
            binding.setVariable("DictionaryUtil", DictionaryUtil.class);
            binding.setVariable("SystemUtil", SystemUtil.class);
            binding.setVariable("CacheUtil", CacheUtil.class);

            GroovyShell shell = new GroovyShell(binding, getConfiguration());
            Object result = shell.evaluate(script);
            if (writer == null) {
                printWriter.println("执行结果：" + (Tool.isBlank(result) ? "" : GsonUtil.toJson(result)));
            }
            jdbcUtil.commit();
            return result;
        } catch (Exception e) {
            jdbcUtil.rollback();
            printWriter.println(e);
            if (e instanceof RuntimeException) {
                throw new CommonException(e.getMessage());
            } else {
                throw new CommonException("脚本运行异常！", e);
            }
        } finally {
            jdbcUtil.close();
            if (writer == null) {
                String duration = DateUtil.formatDuration(System.currentTimeMillis() - startTime);
                printWriter.println("执行结束，耗时：" + duration + "。");
            }
        }
    }

    private static CompilerConfiguration getConfiguration() {
        SecureASTCustomizer customizer = new SecureASTCustomizer();
        customizer.setIndirectImportCheckEnabled(true);
        customizer.setDisallowedStarImports(List.of("groovy.lang"));
        customizer.setDisallowedReceiversClasses(List.of(
                File.class,
                Path.class,
                System.class,
                Runtime.class,
                Process.class,
                ProcessBuilder.class,
                GroovyShell.class,
                ScriptEngineManager.class));
        customizer.addExpressionCheckers(expression -> {
            if (expression instanceof MethodCallExpression expr && "execute".equals(expr.getMethod().getText())) {
                return expr.getObjectExpression() instanceof VariableExpression expr2 && utilList.contains(expr2.getName());
            } else if (expression instanceof DeclarationExpression expr) {
                return (!(expr.getRightExpression() instanceof ClassExpression expr2) || notExistsName(expr2.getType().getName()))
                        && (!(expr.getRightExpression() instanceof ConstructorCallExpression expr3) || notExistsName(expr3.getType().getName()))
                        && (!(expr.getRightExpression() instanceof MethodCallExpression expr4)
                        || !(expr4.getMethod().getText().equals("forName") || expr4.getMethod().getText().equals("loadClass"))
                        || notExistsName(expr4.getArguments()));
            }
            return true;
        });

        CompilerConfiguration configuration = new CompilerConfiguration();
        configuration.addCompilationCustomizers(customizer);
        return configuration;
    }

    private static boolean notExistsName(Expression expr) {
        if (expr instanceof ArgumentListExpression expr2) {
            for (Expression expr3 : expr2.getExpressions()) {
                if (!notExistsName(expr3.getText())) {
                    return false;
                }
            }
        }
        return notExistsName(expr.getText());
    }

    private static boolean notExistsName(String name) {
        List<String> nameList = List.of(
                "java.io.File",
                "java.nio.file.Path",
                "java.lang.System",
                "java.lang.Runtime",
                "java.lang.Process",
                "java.lang.ProcessBuilder",
                "groovy.lang.GroovyShell",
                "javax.script.ScriptEngineManager");
        return nameList.stream().noneMatch(name::equals);
    }
}
